package com.example.myspringboot.runner;

import cn.hutool.core.util.IdUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.example.myspringboot.config.RedisOperate;
import com.example.myspringboot.netty.DmNettyServerHandle;
import com.example.myspringboot.netty.ReadTimeoutHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * @author cy
 * @date 2024/1/5
 */
@Component
public class ApplicationRun implements ApplicationRunner {
    @Autowired
    private ConfigurableApplicationContext context;

    @Value("${server.port}")
    private String port;

    @Value("${socket.port}")
    private int socketPort;

    @Value("${time.out}")
    private int socketTimeout;

    private static final String LOG_ID_KEY = "logId";

    private static final int BOSS_GROUP_THREADS = 5;
    private static final int WORK_GROUP_THREADS = 100; // 假设值，根据实际情况调整

    /**
     * 注入Spring环境对象，用于获取系统环境变量和配置信息
     */
    @Autowired
    private Environment env;

    private static final Log log = LogFactory.get();

    @Resource
    private RedisOperate redisOperate;

    /**
     * 运行方法，用于启动服务
     *
     * @param args 应用程序参数
     * @throws Exception 如果启动服务过程中发生异常
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 打印端口号和URL
        printPortAndUrl();

        startedTcpServer();

        long increment = redisOperate.getIncrement("test");
        System.out.println(increment);
    }

    /**
     * 启动TCP服务器。
     * 此方法配置并启动Netty服务器，用于监听特定端口上的TCP连接。
     * 它使用NIO（非阻塞I/O）模式，并配置了各种处理器来处理进来的连接和数据。
     *
     * @throws InterruptedException 如果线程在等待时被中断
     */
    private void startedTcpServer() throws InterruptedException {
        // 创建Boss线程组，负责接受进来的连接
        EventLoopGroup bossGroup = new NioEventLoopGroup(BOSS_GROUP_THREADS);
        // 创建工作线程组，负责处理连接的I/O操作
        EventLoopGroup workGroup = new NioEventLoopGroup(WORK_GROUP_THREADS);

        try {
            // 创建ServerBootstrap对象，用于配置服务器
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 配置线程组和通道类型
            bootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                    // 配置子通道（即接受的连接）的处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 尝试为每个连接设置唯一的日志ID
                            try {
                                MDC.put(LOG_ID_KEY, IdUtil.getSnowflakeNextIdStr());
                            } catch (IllegalArgumentException e) {
                                log.warn(e.getMessage(), e);
                            }

                            // 添加各种处理器到通道的pipeline中
    //                        socketChannel.pipeline().addFirst(new LoggingHandler());
                            // 添加UTF-8解码器和编码器
                            socketChannel.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8), new StringEncoder(StandardCharsets.UTF_8));
                            // 添加空闲状态处理器，用于处理连接的空闲状态
                            socketChannel.pipeline().addLast(new IdleStateHandler(socketTimeout, socketTimeout, socketTimeout * 2L, TimeUnit.SECONDS));
                            // 添加读超时处理器，用于处理读操作的超时
                            socketChannel.pipeline().addLast(new ReadTimeoutHandler());
                            // 添加自定义的业务逻辑处理器
                            socketChannel.pipeline().addLast(new DmNettyServerHandle());
                        }
                    });

            log.info("服务启动: {}", socketPort);

            // 绑定端口并同步等待服务器启动
            ChannelFuture sync = bootstrap.bind(socketPort).sync();
            // 等待通道关闭
            sync.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }


    /**
     * 打印所有@RestController注解的控制器的URL路径。
     * 该方法通过遍历应用上下文中所有带有@RestController注解的bean，获取其@RequestMapping注解定义的URL路径，
     * 并结合本地主机地址和端口号，打印出完整的URL路径。
     *
     * @throws UnknownHostException 如果无法获取本地主机地址抛出此异常。
     */
    private void printPortAndUrl() throws UnknownHostException {
        String[] namesForAnnotation = context.getBeanNamesForAnnotation(RestController.class);
        // 获取本地主机的IPv4地址
        InetAddress localHost = Inet4Address.getLocalHost();

        // 遍历所有@RestController注解的bean
        for (String name : namesForAnnotation) {
            // 获取bean实例
            Object bean = context.getBean(name);
            // 获取bean的类
            Class<?> aClass = bean.getClass();
            // 获取类级别的@RequestMapping注解
            RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);

            // 如果类上有@RequestMapping注解
            if (annotation != null) {
                // 获取类@RequestMapping注解的第一个值，作为类的根路径
                String[] value = annotation.value();
                // 获取URL路径
                String urlPath = value[0];
                // 遍历类的所有方法
                for (Method method : aClass.getDeclaredMethods()) {
                    // 获取方法级别的@RequestMapping注解
                    RequestMapping mapping = method.getAnnotation(RequestMapping.class);
                    // 如果方法上没有@RequestMapping注解，则跳过
                    if (mapping == null) {
                        continue;
                    }
                    // 获取方法@RequestMapping注解的第一个值，作为方法的路径
                    String urlPathSon = mapping.value()[0];
                    // 使用日志记录完整的URL路径，包括本地主机地址、端口号、类路径和方法路径
                    log.info("http://{}:{}{}{}", localHost.getHostAddress(), port, urlPath, urlPathSon);
                }
            }
        }
    }


}
